package compiler._nfa;

import compiler.enums.Token;
import compiler.error.ErrorHandler;
import compiler.lexer.Lexer;

import java.util.Set;

public class NfaMachineConstructor {
    private Lexer lexer;
    private NfaManager nfaManager = null;

    public NfaMachineConstructor(Lexer lexer) {
        this.lexer = lexer;
        nfaManager = new NfaManager();

        if (lexer.MatchToken(Token.EOS)) {
            lexer.advance();
        }
    }

    private boolean firstInCat(Token token) {
        switch (token) {
            //正确的表达式不会以 ) $ 开头,如果遇到EOS表示正则表达式解析完毕，那么就不应该执行该函数
            case CLOSE_PAREN:
            case AT_EOL:
            case OR:
            case EOS:
                return false;
            case CLOSURE:
            case PLUS_CLOSE:
            case OPTIONAL:
                //*, +, ? 这几个符号应该放在表达式的末尾
                ErrorHandler.parseErr(ErrorHandler.Error.E_CLOSE);
                return false;
            case CCL_END:
                //表达式不应该以]开头
                ErrorHandler.parseErr(ErrorHandler.Error.E_BRACKET);
                return false;
            case AT_BOL:
                //^必须在表达式的最开始
                ErrorHandler.parseErr(ErrorHandler.Error.E_BOL);
                return false;
        }

        return true;
    }

    public void catExpr(NfaPair pairOut) {
        /*
         * catExpr -> factor factor .....
    	 * 由于多个factor 前后结合就是一个cat_expr所以
    	 * catExpr-> factor catExpr
    	 */

        if (firstInCat(lexer.getCurrentToken())) {
            factor(pairOut);
        }

        while (firstInCat(lexer.getCurrentToken())) {
            NfaPair localPair = new NfaPair();
            factor(localPair);

            pairOut.endNode.next = localPair.startNode;

            pairOut.endNode = localPair.endNode;
        }
    }

    public void expr(NfaPair pairOut) {
        /*
         * expr 由一个或多个cat_expr 之间进行 OR 形成
    	 * 而 catExpr -> factor factor …..
    	 * 如果表达式只有一个cat_expr 那么expr 就等价于cat_expr
    	 * 如果表达式由多个cat_expr做或连接构成那么 expr-> cat_expr | cat_expr | ....
    	 * 由此得到expr的语法描述为:
    	 * expr -> expr OR catExpr
    	 *         | catExpr
    	 *
    	 */

        catExpr(pairOut);
        NfaPair localPair = new NfaPair();

        while (lexer.MatchToken(Token.OR)) {
            lexer.advance();
            catExpr(localPair);

            Nfa startNode = nfaManager.newNfa();
            startNode.next2 = localPair.startNode;
            startNode.next = pairOut.startNode;
            pairOut.startNode = startNode;

            Nfa endNode = nfaManager.newNfa();
            pairOut.endNode.next = endNode;
            localPair.endNode.next = endNode;
            pairOut.endNode = endNode;
        }
    }


    public void factor(NfaPair pairOut) {

        /**
         * factor -> term | term* | term+ | term?
         */

        //term
        boolean termCompiled = term(pairOut);
        if (!termCompiled) {
            ErrorHandler.parseErr(ErrorHandler.Error.E_BADEXPR);
        }

        //term*
        boolean handled = constructStarClosure(pairOut);

        if (!handled) {
            //term+
            handled = constructPlusClosure(pairOut);
        }

        if (!handled) {
            //term?
            constructOptionsClosure(pairOut);
        }
    }

    public boolean constructStarClosure(NfaPair pairOut) {
        /*
         * term*
    	 */

        if (!lexer.MatchToken(Token.CLOSURE)) {
            return false;
        }

        Nfa start, end;
        start = nfaManager.newNfa();
        end = nfaManager.newNfa();

        start.next = pairOut.startNode;
        pairOut.endNode.next = end;

        start.next2 = end;
        pairOut.endNode.next2 = pairOut.startNode;

        pairOut.startNode = start;
        pairOut.endNode = end;

        lexer.advance();

        return true;
    }

    public boolean constructPlusClosure(NfaPair pairOut) {
        /*
         * term+
    	 */

        if (!lexer.MatchToken(Token.PLUS_CLOSE)) {
            return false;
        }

        Nfa start, end;
        start = nfaManager.newNfa();
        end = nfaManager.newNfa();

        start.next = pairOut.startNode;
        pairOut.endNode.next = end;

        pairOut.endNode.next2 = pairOut.startNode;

        pairOut.startNode = start;
        pairOut.endNode = end;

        lexer.advance();
        return true;
    }

    public boolean constructOptionsClosure(NfaPair pairOut) {
        /*
    	 * term?
    	 */

        if (!lexer.MatchToken(Token.OPTIONAL)) {
            return false;
        }

        Nfa start, end;
        start = nfaManager.newNfa();
        end = nfaManager.newNfa();

        start.next = pairOut.startNode;
        pairOut.endNode.next = end;

        start.next2 = end;

        pairOut.startNode = start;
        pairOut.endNode = end;

        lexer.advance();
        return true;
    }

    public boolean term(NfaPair pairOut) {
        /*
         * term ->  term表示  a(character) | .(any) | [...] | [..-..] | [^...]
         * tip: except /r/t
         */

        boolean handled = constructNfaForSingleCharacter(pairOut);
        if (!handled) {
            handled = constructNfaForDot(pairOut);
        }

        if (!handled) {
            handled = constructNfaForCharacterSet(pairOut);
        }

        return handled;
    }

    public boolean constructNfaForSingleCharacter(NfaPair pairOut) {
        if (!lexer.MatchToken(Token.L)) {
            return false;
        }

        Nfa start;
        start = pairOut.startNode = nfaManager.newNfa();
        pairOut.endNode = pairOut.startNode.next = nfaManager.newNfa();

        start.setEdge(lexer.getLexeme());

        lexer.advance();

        return true;
    }

    public boolean constructNfaForDot(NfaPair pairOut) {
        if (!lexer.MatchToken(Token.ANY)) {
            return false;
        }

        Nfa start;
        start = pairOut.startNode = nfaManager.newNfa();
        pairOut.endNode = pairOut.startNode.next = nfaManager.newNfa();

        start.setEdge(Nfa.CCL);
        start.addToSet((byte) '\n');
        start.addToSet((byte) '\r');
        start.setComplement();

        lexer.advance();

        return true;
    }

    public boolean constructNfaForCharacterSet(NfaPair pairOut) {
        if (!lexer.MatchToken(Token.CCL_START)) {
            return false;
        }

        lexer.advance();
        boolean negative = false;
        if (lexer.MatchToken(Token.AT_BOL)) {
            negative = true;
        }

        Nfa start;
        start = pairOut.startNode = nfaManager.newNfa();
        pairOut.endNode = pairOut.startNode.next = nfaManager.newNfa();
        start.setEdge(Nfa.CCL);

        doDash(start.inputSet);

        if (!lexer.MatchToken(Token.CCL_END)) {
            ErrorHandler.parseErr(ErrorHandler.Error.E_BADEXPR);
        }

        if (negative) {
            start.addToSet((byte) '\n');
            start.addToSet((byte) '\r');
            start.setComplement();
        }

        lexer.advance();

        return true;
    }

    private void doDash(Set<Byte> set) {
        int first = 0;

        while (!lexer.MatchToken(Token.EOS)
                && !lexer.MatchToken(Token.CCL_END)) {

            if (!lexer.MatchToken(Token.DASH)) {
                first = lexer.getLexeme();
                set.add((byte) first);
                lexer.advance();
                continue;
            }

            lexer.advance(); //越过 -

            for (; first <= lexer.getLexeme(); first++) {
                set.add((byte) first);
            }

            lexer.advance();
        }
    }
}
